package com.dly.blog.provider;

import com.dly.blog.exception.AuthExceptionEnum;
import com.dly.blog.service.DefaultDetailsService;
import com.dly.blog.token.AccountToken;
import com.dly.blog.token.EmailToken;
import jakarta.annotation.Resource;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.*;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.NullAuthoritiesMapper;
import org.springframework.security.core.userdetails.UserCache;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsChecker;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.core.userdetails.cache.NullUserCache;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;


/**
 * @Author: DaiLiYu
 * @Date: 2023/7/12 16:19
 */
@EqualsAndHashCode(callSuper = true)
@Data
@Slf4j
@Configuration
public class EmailAuthenticationProvider extends BaseAuthenticationProvider implements AuthenticationProvider  {

    @Resource
    private DefaultDetailsService defaultDetailsService;

    private UserCache userCache = new NullUserCache();

    protected boolean hideUserNotFoundExceptions = true;

    private boolean forcePrincipalAsString = false;

    private GrantedAuthoritiesMapper authoritiesMapper = new NullAuthoritiesMapper();

    protected UserDetails retrieveUser(String username, EmailToken authentication) throws AuthenticationException {
        prepareTimingAttackProtection();
        try {
            UserDetails loadedUser = defaultDetailsService.loadUserByUsername(username);
            if (loadedUser == null) {
                throw new InternalAuthenticationServiceException(
                        "UserDetailsService returned null, which is an interface contract violation");
            }
            return loadedUser;
        }
        catch (UsernameNotFoundException ex) {
            mitigateAgainstTimingAttack(authentication);
            throw ex;
        }
        catch (InternalAuthenticationServiceException ex) {
            throw ex;
        }
        catch (Exception ex) {
            throw new InternalAuthenticationServiceException(ex.getMessage(), ex);
        }
    }

    @Override
    public Authentication authenticate(Authentication authentication) throws AuthenticationException {
        String username = determineUsername(authentication);

        boolean cacheWasUsed = true;
        UserDetails user = this.userCache.getUserFromCache(username);
        if (user == null) {
            cacheWasUsed = false;
            try {
                user = retrieveUser(username, (EmailToken) authentication);
            }
            catch (UsernameNotFoundException ex) {
                log.debug("Failed to find user '" + username + "'");
                if (!this.hideUserNotFoundExceptions) {
                    throw ex;
                }
                throw new BadCredentialsException("AbstractUserDetailsAuthenticationProvider.badCredentials Bad credentials");
            }
            Assert.notNull(user, "retrieveUser returned null - a violation of the interface contract");
        }

        preAuthenticationChecks.check(user);

        additionalAuthenticationChecks(user, (EmailToken) authentication);
        this.postAuthenticationChecks.check(user);
        if (!cacheWasUsed) {
            this.userCache.putUserInCache(user);
        }

        Object principalToReturn = user;
        if (this.forcePrincipalAsString) {
            principalToReturn = user.getUsername();
        }

        EmailToken authenticated =  EmailToken.authenticated(principalToReturn, authentication.getCredentials(), authoritiesMapper.mapAuthorities(user.getAuthorities()));
        authenticated.setDetails(authentication.getDetails());
        return authenticated;
    }

    @Override
    public boolean supports(Class<?> authentication) {
        return EmailToken.class.isAssignableFrom(authentication);
    }
}
